package org.saxion.concurrency;

/**
 * 
 * @author Mathijs de Vries, Jeroen Katgert
 * 
 */
import java.util.ArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ShowRoom {

	Lock lock;

	private static final int NR_OF_SEATS = 20;
	private static final int FIRST_PERSON = 0;
	private int buyerStreak = 0;

	private boolean invited = false;

	private Condition freeSeat, someoneMightEnter, buyerMayEnter,
			viewerMayEnter, newPerson, invitationAccepted;

	public ArrayList<Person> showRoomQueue = new ArrayList<Person>();
	private ArrayList<Person> queue = new ArrayList<Person>();

	public ShowRoom() {
		lock = new ReentrantLock();
		freeSeat = lock.newCondition();
		someoneMightEnter = lock.newCondition();
		buyerMayEnter = lock.newCondition();
		viewerMayEnter = lock.newCondition();
		newPerson = lock.newCondition();
		invitationAccepted = lock.newCondition();
	}

	public void enterQueue() {
		lock.lock();
		try {
			while (queue.size() >= NR_OF_SEATS) {
				freeSeat.await();
			}
			queue.add((Person) Thread.currentThread());
			System.out.println("Person " + Thread.currentThread().getName()
					+ " enters the queue, queueSize =" + queue.size());
			newPerson.signal();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

	public void waitOnViewerInvite() {
		lock.lock();
		try {
			while (!invited) {
				viewerMayEnter.await();
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

	public void waitOnBuyerInvite() {
		lock.lock();
		try {
			while (!invited) {
				buyerMayEnter.await();
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

	public void moveToShowRoom() {
		lock.lock();
		try {
			queue.remove(Thread.currentThread());
			freeSeat.signal();
			showRoomQueue.add((Person) Thread.currentThread());
			System.out.println(Thread.currentThread().getName()
					+ " enters the showRoom. \nSize of the queue = "
					+ queue.size());
			System.out
					.println("Size of the showroom = " + showRoomQueue.size());
			invited = false;
			invitationAccepted.signal();
		} finally {
			lock.unlock();
		}
	}

	public void leaveShowRoom() {
		lock.lock();
		try {
			showRoomQueue.remove(Thread.currentThread());
			System.out.println(Thread.currentThread().getName()
					+ " leaves the showRoom, size of the showRoom = "
					+ showRoomQueue.size());
			someoneMightEnter.signal();
		} finally {
			lock.unlock();
		}
	}

	public void nextPerson() {
		lock.lock();
		try {
			while (queue.isEmpty()) {
				newPerson.await();
				System.out.println("Bouncer noticed a new person");
			}
			while (!buyerMayEnter() && !viewerMayEnter()) {
				System.out.println("Nobody may enter yet");
				someoneMightEnter.await();
				System.out.println("If I see this print, someone may enter");
			}
			if (buyerMayEnter()) {
				buyerStreak++;
				invited = true;
				buyerMayEnter.signal();
			} else if (viewerMayEnter()) {
				System.out.println("Why u print so much?");
				buyerStreak = 0;
				invited = true;
				viewerMayEnter.signal();
			}
			while(invited){
				invitationAccepted.await();
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

	private boolean buyerMayEnter() {
		lock.lock();
		try {
			return buyersInQueue() && (buyerStreak < 3 || !viewersInQueue())
					&& showRoomQueue.isEmpty();
		} finally {
			lock.unlock();
		}
	}

	private boolean viewerMayEnter() {
		lock.lock();
		try {
			return viewersInQueue() && (!buyersInQueue() || buyerStreak >= 3)
					&& !(showRoomContainsBuyer());

		} finally {
			lock.unlock();
		}
	}

	private boolean showRoomContainsBuyer() {
		lock.lock();
		try {
			if (showRoomQueue.isEmpty()) {
				return false;
			} else {
				return showRoomQueue.get(FIRST_PERSON) instanceof Buyer;
			}
		} finally {
			lock.unlock();
		}
	}

	private boolean buyersInQueue() {
		lock.lock();
		try {
			for (Person person : queue) {
				if (person instanceof Buyer) {
					return true;
				}
			}
			return false;
		} finally {
			lock.unlock();
		}
	}

	private boolean viewersInQueue() {
		lock.lock();
		try {
			for (Person person : queue) {
				if (person instanceof Viewer) {
					return true;
				}
			}
			return false;
		} finally {
			lock.unlock();
		}
	}

	public int getQueueSize() {
		return queue.size();
	}

}